package com.leyou.auth.service;

import com.leyou.auth.config.JwtProperties;
import com.leyou.common.auth.pojo.Payload;
import com.leyou.common.auth.pojo.UserInfo;
import com.leyou.common.auth.utils.JwtUtils;
import com.leyou.common.constants.RedisConstants;
import com.leyou.common.constants.UserTokenConstants;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.CookieUtils;
import com.leyou.user.client.UserClient;
import com.leyou.user.dto.UserDTO;
import feign.FeignException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

/**
 * @author 虎哥
 */
@Service
public class UserAuthService {

    @Autowired
    private UserClient userClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private JwtProperties prop;

    public void login(String username, String password, HttpServletResponse response) {
        // 1.查询用户
        UserDTO user = null;
        try {
            user = userClient.queryUserByUsernameAndPassword(username, password);
        } catch (FeignException e) {
            // 捕获feign的异常，并获取feign调用的状态码和异常信息
            throw new LyException(e.status(), e.contentUTF8(), e);
        }
        // 2.判断查询结果
        if (user == null) {
            // 查询失败，一定是用户名或密码错误
            throw new LyException(400, "用户名或密码错误");
        }
        // 3.生成token
        // 3.1.生成JTI
        String jti = JwtUtils.createJTI();
        // 3.2.生成token
        String token = JwtUtils.generateTokenWithJTI(
                new UserInfo(user.getId(), user.getUsername()), jti, prop.getPrivateKey());
        // 4.写入cookie
        CookieUtils.builder(response)
                .httpOnly(true) // 限制cookie不能用JS来获取，避免XSS攻击
                .domain(UserTokenConstants.DOMAIN) // cookie的域名
                .build(UserTokenConstants.COOKIE_NAME, token);
        // 5.把token的id写入redis，并设置有效期为30分钟
        redisTemplate.opsForValue().set(
                RedisConstants.AUTH_LOGIN_KEY_PREFIX + user.getId(),
                jti, RedisConstants.TOKEN_EXPIRE_MINUTES, TimeUnit.MINUTES);
    }

    public UserInfo verify(HttpServletRequest request) {

        // 1.服务端获取cookie
        String token = CookieUtils.getCookieValue(request, UserTokenConstants.COOKIE_NAME);
        // 2.校验cookie中的token的有效性
        Payload<UserInfo> payload = null;
        try {
            payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), UserInfo.class);
        } catch (Exception e) {
            throw new LyException(401, "未登录或登录超时");
        }
        // 3.获取token中的用户信息
        UserInfo userInfo = payload.getUserInfo();

        // 4.校验token是否与redis中一致
        // 4.1.取出redis中的JTI
        String cacheJTI = redisTemplate.opsForValue()
                .get(RedisConstants.AUTH_LOGIN_KEY_PREFIX + userInfo.getId());
        // 4.2.与token中的JTI比较
        if(!StringUtils.equals(cacheJTI, payload.getId())){
            // tokenID与redis中的不一致，应该是无效的token
            throw new LyException(401, "未登录或登录超时");
        }
        // 5.返回用户信息
        return userInfo;
    }


    public void logout(HttpServletRequest request, HttpServletResponse response) {
        // 1.获取用户cookie
        String token = CookieUtils.getCookieValue(request, UserTokenConstants.COOKIE_NAME);
        // 2.校验cookie中的token的有效性
        Payload<UserInfo> payload = null;
        try {
            payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), UserInfo.class);
        } catch (Exception e) {
            // 3.如果无效，什么都不做
            return;
        }
        // 4.如果有效，删除cookie（重新写一个cookie，maxAge为0）
        CookieUtils.deleteCookie(UserTokenConstants.COOKIE_NAME, UserTokenConstants.DOMAIN, response);

        // 5.删除redis中的JTI
        // 5.1.获取用户信息
        UserInfo userInfo = payload.getUserInfo();
        // 5.2.删除redis数据
        redisTemplate.delete(RedisConstants.AUTH_LOGIN_KEY_PREFIX + userInfo.getId());
    }
}
